package cn.trigram.json.deserializer;

import static cn.trigram.date.DateUtil.DASH_FORMAT_FULL;
import static cn.trigram.date.DateUtil.DASH_FORMAT_SHORT;
import static cn.trigram.date.DateUtil.DATETIME_PATTERN;
import static cn.trigram.date.DateUtil.DATE_PATTERN;
import static cn.trigram.date.DateUtil.FORMAT_TIME;
import static cn.trigram.date.DateUtil.GF_DATETIME_PATTERN;
import static cn.trigram.date.DateUtil.GF_DATE_PATTERN;
import static cn.trigram.date.DateUtil.GF_TIME_PATTERN;
import static cn.trigram.date.DateUtil.LEFT_SLASH_FORMAT_FULL;
import static cn.trigram.date.DateUtil.LEFT_SLASH_FORMAT_SHORT;
import static cn.trigram.date.DateUtil.RIGHT_SLASH_FORMAT_FULL;
import static cn.trigram.date.DateUtil.RIGHT_SLASH_FORMAT_SHORT;
import static cn.trigram.date.DateUtil.TIME_PATTERN;
import static java.time.temporal.ChronoField.EPOCH_DAY;
import static java.time.temporal.ChronoField.NANO_OF_DAY;
import static java.util.Optional.ofNullable;

import cn.trigram.date.DateUtil;
import cn.trigram.exception.DateFormatException;
import cn.trigram.json.annotation.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.google.common.base.Strings;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.regex.Matcher;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * 日期时间的反序列化，包括Date和jdk8以后的Temporal类型
 *
 * @version 1.0.0
 * @date 2022/08/23 10:09:09
 * @since 1.0.0
 */
public class TemporalContextualDeserializer<T> extends JsonDeserializer<T> implements ContextualDeserializer {

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) throws JsonMappingException {
        DateTimeFormat format = ofNullable(property).map(p -> p.getAnnotation(DateTimeFormat.class)).orElse(null);
        final String serializer = Strings.emptyToNull(ofNullable(format).map(DateTimeFormat::serializer).orElse(null));
        final Class<?> rawClass = ofNullable(property).map(BeanProperty::getType).map(JavaType::getRawClass).orElse(null);
        if (Objects.equals(rawClass, Date.class)) {
            if (Objects.isNull(format)) {
                return new SoftReference<>(new DateDeserializers.DateDeserializer().createContextual(context, property)).get();
            } else {
                return new SoftReference<>(new DateDeserializer(format)).get();
            }
        } else {
            if (Objects.equals(rawClass, LocalDate.class)) {
                return new SoftReference<>(new LocalDateDeserializer(
                        DateUtil.formatter(ObjectUtils.defaultIfNull(serializer, DateUtil.GF_DEFAULT_SHORT))
                ).createContextual(context, property)).get();
            } else if (Objects.equals(rawClass, LocalDateTime.class)) {
                return new SoftReference<>(new LocalDateTimeDeserializer(
                        DateUtil.formatter(ObjectUtils.defaultIfNull(serializer, DateUtil.GF_DEFAULT_FULL))
                ).createContextual(context, property)).get();
            } else if (Objects.equals(rawClass, LocalTime.class)) {
                return new SoftReference<>(new LocalTimeDeserializer(
                        DateUtil.formatter(ObjectUtils.defaultIfNull(serializer, DateUtil.GF_DEFAULT_TIME))
                ).createContextual(context, property)).get();
            } else if (Objects.isNull(property)) {
                return new SoftReference<>(new DateDeserializers.DateDeserializer().createContextual(context, property)).get();
            } else {
                return context.findContextualValueDeserializer(Optional.of(property).map(BeanProperty::getType).orElse(null), property);
            }
        }
    }

    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) {
        return null;
    }

    /**
     * @version 1.0.0
     * @date 2022/08/02 16:09:59
     * @since 1.0.0
     */
    @JacksonStdImpl
    public static class DateDeserializer extends StdDeserializer<Date> {
        private final DateTimeFormat format;

        public DateDeserializer(DateTimeFormat format) {
            super(Date.class);
            this.format = format;
        }

        @Override
        public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            String text = p.getValueAsString();
            if (StringUtils.isBlank(text)) {
                return getNullValue(ctxt);
            }

            String guessFormat = null;
            if (StringUtils.isNotBlank(this.format.deserializer())) {
                guessFormat = this.format.deserializer();
            } else {
                Matcher matcher = GF_DATE_PATTERN.matcher(text);
                if (matcher.matches()) {
                    guessFormat = DateUtil.GF_DEFAULT_SHORT;
                }
                matcher = GF_DATETIME_PATTERN.matcher(text);
                if (matcher.matches()) {
                    guessFormat = DateUtil.GF_DEFAULT_FULL;
                }
                matcher = GF_TIME_PATTERN.matcher(text);
                if (matcher.matches()) {
                    guessFormat = DateUtil.GF_DEFAULT_TIME;
                }
                //这里两个Pattern的匹配顺序不能错，要保证先匹配长的模式
                matcher = DATETIME_PATTERN.matcher(text);
                if (matcher.matches()) {
                    final String joinSymbol = matcher.group(1);
                    switch (joinSymbol) {
                        case "-":
                            guessFormat = DASH_FORMAT_FULL;
                            break;
                        case "/":
                            guessFormat = RIGHT_SLASH_FORMAT_FULL;
                            break;
                        case "\\":
                            guessFormat = LEFT_SLASH_FORMAT_FULL;
                            break;
                        default:
                            guessFormat = null;
                            break;
                    }
                }
                matcher = DATE_PATTERN.matcher(text);
                if (matcher.matches()) {
                    final String joinSymbol = matcher.group(1);
                    switch (joinSymbol) {
                        case "-":
                            guessFormat = DASH_FORMAT_SHORT;
                            break;
                        case "/":
                            guessFormat = RIGHT_SLASH_FORMAT_SHORT;
                            break;
                        case "\\":
                            guessFormat = LEFT_SLASH_FORMAT_SHORT;
                            break;
                        default:
                            guessFormat = null;
                            break;
                    }
                }
                matcher = TIME_PATTERN.matcher(text);
                if (matcher.matches()) {
                    guessFormat = FORMAT_TIME;
                }
            }
            if (StringUtils.isBlank(guessFormat)) {
                throw new DateFormatException("未知时间格式，请在deserializer中指定格式");
            }
            final TemporalAccessor temporalAccessor = DateUtil.formatter(guessFormat, this.format.locale(), this.format.timezone())
                    .parse(text);
            if (temporalAccessor.isSupported(ChronoField.INSTANT_SECONDS)) {
                //这个判断表示支持Instant
                return Date.from(Instant.from(temporalAccessor));
            } else {
                final String timezoneStr = this.format.timezone();
                ZoneId zoneId = !StringUtils.equals(timezoneStr, JsonFormat.DEFAULT_TIMEZONE) && StringUtils.isNotBlank(timezoneStr)
                        ? TimeZone.getTimeZone(timezoneStr).toZoneId()
                        : ZoneId.systemDefault();
                if (temporalAccessor.isSupported(ChronoField.EPOCH_DAY) && !temporalAccessor.isSupported(ChronoField.NANO_OF_DAY)) {
                    // 这个判断表示支持LocalDate，但是LocalTime以当天的00:00:00
                    return Date.from(LocalDate.ofEpochDay(temporalAccessor.getLong(EPOCH_DAY)).atStartOfDay()
                            .atZone(zoneId).toInstant());
                } else if (!temporalAccessor.isSupported(ChronoField.EPOCH_DAY) && temporalAccessor.isSupported(ChronoField.NANO_OF_DAY)) {
                    // 这个判断表示支持LocalTime，但是LocalDate部分以当天为值
                    return Date.from(LocalTime.ofNanoOfDay(temporalAccessor.getLong(NANO_OF_DAY)).atDate(LocalDate.now())
                            .atZone(zoneId).toInstant());
                } else if (temporalAccessor.isSupported(ChronoField.EPOCH_DAY) && temporalAccessor.isSupported(ChronoField.NANO_OF_DAY)) {
                    return Date.from(LocalDateTime.of(LocalDate.ofEpochDay(temporalAccessor.getLong(EPOCH_DAY)), LocalTime.ofNanoOfDay(temporalAccessor.getLong(NANO_OF_DAY)))
                            .atZone(zoneId).toInstant());
                }
            }
            throw new UnsupportedTemporalTypeException("不支持的日期时间值【" + text + "】");
        }

    }
}
